// Copyright 2012 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2012 Petar Maymounkov <p@gocircuit.org>

package ripple

import (
	"fmt"
	"log"
	"math/rand"
	"sync"
	"time"

	"circuit/kit/rh"
	"circuit/kit/rh/tokenlock"
)

// lockFID
type lockFID struct {
	rh.AnchoredFID
	sync.Mutex
	//
	q     rh.Q
	gtm   time.Time
	tlock tokenlock.Lock
	token tokenlock.Token
}

func NewLock(tlock tokenlock.Lock) rh.FID {
	fid := &lockFID{
		tlock: tlock,
		gtm:   time.Now(),
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
	}
	return fid
}

func (fid *lockFID) String() string {
	return fmt.Sprintf("lock (%p) %v", fid, fid.Q())
}

func (fid *lockFID) Q() rh.Q {
	return fid.q
}

func (fid *lockFID) hasToken() bool {
	return fid.token != nil && fid.token.IsValid()
}

func (fid *lockFID) Open(flag rh.Flag, intr rh.Intr) (err error) {
	defer un(lock(fid))

	// avoid blocking simulated opens from the fuse/rh setattr hack
	if flag.Create || flag.Truncate || flag.Deny {
		return rh.ErrPerm
	}

	// Block open until lock obtained
	if fid.token, err = fid.tlock.Acquire(intr); err != nil {
		return rh.ErrGone
	}
	return nil
}

func (fid *lockFID) Read(_ int64, count int, intr rh.Intr) (chunk rh.Chunk, err error) {
	defer un(lock(fid))
	defer func() {
		log.Printf("read %v", err)
	}()
	if !fid.hasToken() {
		return nil, rh.ErrGone
	}
	return 0, nil
}

func (fid *lockFID) Write(offset int64, data rh.Chunk, intr rh.Intr) (n int, err error) {
	defer func() {
		log.Printf("write %v", err)
	}()
	defer un(lock(fid))
	if !fid.hasToken() {
		return 0, rh.ErrGone
	}
	if data == nil {
		// Writing empty data is allowed.
		return 0, nil
	}
	return len(data.(rh.ByteChunk)), nil
}

func (fid *lockFID) Clunk() {
	defer un(lock(fid))
	fid.token.Release()
}

func (fid *lockFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) > 0 {
		return nil, rh.ErrClash
	}
	return NewLock(fid.tlock), nil
}

func (fid *lockFID) Stat() (*rh.Dir, error) {
	return &rh.Dir{
		Q:      fid.q,
		Mode:   rh.Mode{Attr: rh.ModeIO},
		Perm:   0666, // wr-wr-wr-, acts as /dev/null for writing and empty file for reading, when lock acquired
		Atime:  fid.gtm,
		Mtime:  fid.gtm,
		Name:   "lock",
		Length: 0,
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}
